/*
 * Copyright (C) 2009 Yury Kudryashov.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package name.kudryashov.base;

import java.util.NoSuchElementException;

/**
 * Represents a disjoint union, containing either something or something else.
 * @param <L> type of the left value
 * @param <R> type of the right value 
 */
public abstract class Either<L, R> {

    /**
     * @return the left value or throws an exception if this is right
     * @throws NoSuchElementException this is right value
     */
    public L getLeft() {
        throw new NoSuchElementException("Either.Right.left");
    }

    /**
     * @return the right value or throws an exception if this is left
     * @throws NoSuchElementException this is left value
     */
    public R getRight() {
        throw new NoSuchElementException("Either.Left.right");
    }

    /**
     * @return true if this is left, false otherwise
     */
    public abstract boolean isLeft();
    /**
     * @return true if this is right, false otherwise
     */
    public abstract boolean isRight();

    /**
     * @return an {@link Some} value if this is left or {@link None} if this is right.
     */
    public Option<L> getLeftO() {
        return isLeft() ? Option.some(getLeft()) : Option.<L>none();
    }

    /**
     * @return an {@link Some} value if this is right or {@link None} if this is left.
     */
    public Option<R> getRightO() {
        return isRight() ? Option.some(getRight()) : Option.<R>none(); 
    }

    /**
     * @return a new {@link Either} value with left and right swapped.
     */
    @SuppressWarnings({"unchecked"})
    public Either<R, L> swap() {
        return isLeft() ? Either.<R, L>right(getLeft()) : Either.<R, L>left(getRight());
    }


    /**
     * Creates a new left Either value.
     * @param left value
     * @param <L> type of the left value
     * @param <R> type of the right value
     * @return a new left
     */
    public static <L, R> Either<L, R> left(L left) {
        return new Left<L, R>(left);
    }

    /**
     * Creates a new right Either value.
     * @param right value
     * @param <L> type of the left value
     * @param <R> type of the right value
     * @return a new right
     */
    public static <L, R> Either<L, R> right(R right) {
        return new Right<L, R>(right);
    }

    private static class Left<L, R> extends Either<L, R> {

        private final L left;

        public Left(L left) {
            this.left = left;
        }

        public boolean isLeft() {
            return true;
        }

        public boolean isRight() {
            return false;
        }

        @Override
        public L getLeft() {
            return left;
        }

        @Override
        public String toString() {
            return "Either.Left(" + left + ")";
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Left left1 = (Left) o;

            return !(left != null ? !left.equals(left1.left) : left1.left != null);

        }

        @Override
        public int hashCode() {
            return left != null ? left.hashCode() : 0;
        }
    }

    private static class Right<L, R> extends Either<L, R> {

        private final R right;

        public Right(R right) {
            this.right = right;
        }

        public boolean isLeft() {
            return false;
        }

        public boolean isRight() {
            return true;
        }

        @Override
        public R getRight() {
            return right;
        }

        @Override
        public String toString() {
            return "Either.Right(" + right + ")";
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Right right1 = (Right) o;

            return !(right != null ? !right.equals(right1.right) : right1.right != null);
        }

        @Override
        public int hashCode() {
            return right != null ? right.hashCode() : 0;
        }
    }
}
